﻿//    TestGenerator - experiment with piping data into ffmpeg in "pipefmt"
//    Copyright (C) 2012  Juergen Bohl

//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.

//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.

//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.

namespace TestGenerator
{
    #region using directives
    using System;
    #endregion

    internal class AudioGenerator
    {
        private int currentsampleno;
        private int samplefreq;
        private int sinefreq;
        private float volume;
        private int sampleCountSine;
        private int sampleCountSilence;

        public void Init(int samplefreq, int sinefreq, float volume = 1.0f, int sampleCountSine = int.MaxValue, int sampleCountSilence = 0)
        {
            this.samplefreq = samplefreq;
            this.sinefreq = sinefreq;
            this.currentsampleno = 0;
            this.volume = volume;

            if (sampleCountSine < 0 || sampleCountSilence < 0)
            {
                throw new ArgumentException("sampleCountSind and sampleCountSilence cannot be negative.");
            }

            if (sampleCountSine == 0 && this.sampleCountSilence == 0)
            {
                throw new ArgumentException("sampleCountSine and sampleCountSilence cannot both be zero.");
            }

            this.sampleCountSilence = sampleCountSilence;
            this.sampleCountSine = sampleCountSine;
        }

        public short[] GetSamplesMono(int samplecount)
        {
            short s;
            double omega = 2 * Math.PI / this.samplefreq;
            short[] samples = new short[samplecount];
            if (this.volume >= 1.0f)
            {
                for (int i = 0; i < samplecount; i++)
                {
                    double d = Math.Sin(omega * this.currentsampleno * this.sinefreq);
                    s = (short)(short.MaxValue * d);
                    this.currentsampleno++;
                    samples[i] = s;
                }
            }
            else
            {
                double vol = this.volume;
                for (int i = 0; i < samplecount; i++)
                {
                    double d = Math.Sin(omega * this.currentsampleno * this.sinefreq);
                    s = (short)(vol * (short.MaxValue * d));
                    this.currentsampleno++;
                    samples[i] = s;
                }
            }

            return samples;
        }

        public short[] GetSamplesStereo(int samplecount)
        {
            short[] samples = new short[samplecount * 2];
            for (int cnt = 0; cnt < samplecount; )
            {
                int c = this.CalcNumberOfSineSamples(samplecount - cnt);
                if (c > 0)
                {
                    this.GetSineSamplesStereo(c, this.currentsampleno, samples, cnt);
                    this.currentsampleno += c;
                    cnt += c;
                }

                if (cnt < samplecount)
                {
                    // no need to set samples[] to zero because the array is initially already filled with 0's
                    int cS = this.CalcNumberOfSilenceSamples(samplecount - cnt);
                    this.currentsampleno += cS;
                    cnt += cS;
                }
            }

            return samples;
        }

        private void Reset()
        {
            this.currentsampleno = 0;
        }

        private int CalcNumberOfSineSamples(int maxSamples)
        {
            int sum = this.sampleCountSine + this.sampleCountSilence;
            int cycleOffset = this.currentsampleno % sum;
            if (cycleOffset >= this.sampleCountSine)
            {
                return 0;
            }

            return Math.Min(this.sampleCountSine - cycleOffset, maxSamples);
        }

        private int CalcNumberOfSilenceSamples(int maxSamples)
        {
            int sum = this.sampleCountSine + this.sampleCountSilence;
            int cycleOffset = this.currentsampleno % sum;
            if (cycleOffset < this.sampleCountSine)
            {
                return 0;
            }

            return Math.Min(sum - cycleOffset, maxSamples);
        }

        private void GetSineSamplesStereo(int samplecount, int currentSampleNo, short[] samples, int startOffset)
        {
            short s;

            double omega = 2 * Math.PI / this.samplefreq;


            if (this.volume >= 1.0f)
            {
                for (int i = 0; i < samplecount; i++)
                {
                    double d = Math.Sin(omega * currentSampleNo * this.sinefreq);
                    s = (short)(short.MaxValue * d);
                    currentSampleNo++;
                    samples[startOffset * 2 + i * 2] = s;
                    samples[startOffset * 2 + i * 2 + 1] = s;
                }
            }
            else
            {
                double vol = this.volume;
                for (int i = 0; i < samplecount; i++)
                {
                    double d = Math.Sin(omega * currentSampleNo * this.sinefreq);
                    s = (short)(vol * (short.MaxValue * d));
                    currentSampleNo++;
                    samples[startOffset * 2 + i * 2] = s;
                    samples[startOffset - 2 + i * 2 + 1] = s;
                }
            }
        }
    }
}